﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using System.Text.RegularExpressions;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using System.Text;
using System.Threading;


namespace LuceneHelper
{
    public class SearchHelper
    {

        public delegate DocumentsHelper SearchDelegate(string[] indexesPathes, string SearchStatement, Analyzer analyzer, Filter filter, Sort sort, params string[] DefaultFields);
        public delegate void SearchFinishedDelegate(object sender, SearchHelperEventArgs e);
        public event SearchFinishedDelegate SearchFinished;


        private int maximumDocument = 1000000;
        public int MaximumDocument
        {
            get { return maximumDocument; }
            set { maximumDocument = value; }
        }

        private string noiseDataPath = "";
        public string NoiseDataPath
        {
            get { return this.noiseDataPath; }
            set { this.noiseDataPath = value; }
        }

        [ThreadStatic]
        private static Queue<string> doubleQutationQueue = new Queue<string>();

        [ThreadStatic]
        private static Queue<string> fieldsQueue = new Queue<string>();

        [ThreadStatic]
        private static string FieldsTemplateSatatement = "";


        public DocumentsHelper Search(string[] indexesPathes, string SearchStatement, Analyzer analyzer, Filter filter, Sort sort, params string[] DefaultFields)
        {
            List<Exception> errors = new List<Exception>();
            TopDocs TopDocs = null;
            DateTime startTime = new DateTime();
            DateTime endTime = new DateTime();
            MultiSearcher searcher = null;
            SortedList<string, string> searchFieldsDictionary = new SortedList<string, string>();
            SortedList<string, string> displayFieldsDictionary = new SortedList<string, string>();
            string s = SearchStatement;
            FieldsTemplateSatatement = "";
            List<Searchable> Searchablelist = new List<Searchable>();
            List<BooleanClause.Occur> occurs = new List<BooleanClause.Occur>();
            try
            {
                //initial prepare
                s = SearchHelper.prepare(SearchStatement);

                //hide  OriginalDoubleQutation before normalize
                doubleQutationQueue = new Queue<string>();
                MatchEvaluator ev = new MatchEvaluator(SearchHelper.HideDoubleQutation);
                s = Regex.Replace(s, @"""[^""]+""", ev);


                for (int i = 0; i < indexesPathes.Length; i++)
                {
                    FSDirectory fs = FSDirectory.Open(new System.IO.DirectoryInfo(indexesPathes[i]));
                    IndexSearcher indexSearcher = new IndexSearcher(fs,true);
                    Searchablelist.Add(indexSearcher);
                    ICollection<string> searchfields = indexSearcher.GetIndexReader().GetFieldNames(IndexReader.FieldOption.INDEXED);
                    foreach (string key in searchfields)
                    {
                        if (!searchFieldsDictionary.ContainsKey(key.ToLower()))
                        {
                            searchFieldsDictionary[key.ToLower()] = key;

                            //normalize searchStatement (lucene case sensitive)
                            s = Regex.Replace(s, "\\b" + key + ": ", key + ": ", RegexOptions.IgnoreCase);

                            //normalize default fields
                            if (DefaultFields != null && DefaultFields.Length > 0)
                            {
                                for (int j = 0; j < DefaultFields.Length; j++)
                                {
                                    if (DefaultFields[j].ToLower() == key.ToLower())
                                    {
                                        DefaultFields[j] = key;
                                        occurs.Add(BooleanClause.Occur.SHOULD);
                                        if (FieldsTemplateSatatement == "")
                                        {
                                            FieldsTemplateSatatement = "( " + key + ": ( @@@@@ ) )";
                                        }
                                        else
                                        {
                                            FieldsTemplateSatatement += " OR ( " + key + ": ( @@@@@ ) )";
                                        }
                                        break;
                                    }
                                }
                            }

                            //get fields template statement
                            else
                            {
                                if (FieldsTemplateSatatement == "")
                                {
                                    FieldsTemplateSatatement = "( " + key + ": ( @@@@@ ) )";
                                }
                                else
                                {
                                    FieldsTemplateSatatement += " OR ( " + key + ": ( @@@@@ ) )";
                                }
                            }
                        }

                    }
                    FieldsTemplateSatatement = string.Format("( {0} )", FieldsTemplateSatatement);


                    ICollection<string> displayfields = indexSearcher.GetIndexReader().GetFieldNames(IndexReader.FieldOption.ALL);
                    foreach (string key in displayfields)
                    {
                        if (!displayFieldsDictionary.ContainsKey(key.ToLower()))
                        {
                            displayFieldsDictionary[key.ToLower()] = key;
                        }
                    }
                    fs.Close();
                }


                //unhide doubl Quotation terms
                ev = new MatchEvaluator(SearchHelper.UnHideDoubleQutation);
                s = Regex.Replace(s, "@@@@@", ev);


                searcher = new MultiSearcher(Searchablelist.ToArray());

                //set analyze
                if (analyzer == null)
                {
                    analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29,new System.IO.FileInfo(this.noiseDataPath));
                }

                //set MaxClauseCount
                BooleanQuery.SetMaxClauseCount(100000);

                Query query = null;
                s = SearchHelper.getAdvencedQueryString(s, FieldsTemplateSatatement);
                QueryParser qp = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, s, analyzer);
                query = qp.Parse(s);

                startTime = DateTime.Now;
                if (sort == null)
                {
                    TopDocs = searcher.Search(query, filter, this.maximumDocument);
                }
                else
                {
                    TopDocs = searcher.Search(query, filter, this.maximumDocument, sort);
                }
                endTime = DateTime.Now;
            }
            catch (Exception Error)
            {
                errors.Add(Error);
            }

            DocumentsHelper documentsHelper = new DocumentsHelper(errors.ToArray(), searcher, TopDocs, displayFieldsDictionary, s, startTime, endTime);

            return documentsHelper;
        }


        public void BeginSearch(string[] indexesPathes, string SearchStatement, Analyzer analyzer, Filter filter, Sort sort, Hashtable otherInformation, params string[] DefaultFields)
        {
            SearchDelegate m = new SearchDelegate(this.Search);
            m.BeginInvoke(indexesPathes, SearchStatement, analyzer, filter, sort, DefaultFields, new AsyncCallback(this.finish), otherInformation);
        }

        private void finish(IAsyncResult IAsyncRes)
        {
            if (this.SearchFinished != null)
            {
                Hashtable otherInformation = (Hashtable)IAsyncRes.AsyncState;
                AsyncResult ar = (AsyncResult)IAsyncRes;
                SearchDelegate b = (SearchDelegate)ar.AsyncDelegate;
                SearchHelperEventArgs e = new SearchHelperEventArgs(b.EndInvoke(IAsyncRes), otherInformation);
                this.SearchFinished(this, e);
            }
        }


        public static string prepare(string SearchStatement)
        {
            doubleQutationQueue = new Queue<string>();
            string s = SearchStatement;

            //hide original doubl Quotation terms
            MatchEvaluator ev = new MatchEvaluator(SearchHelper.HideDoubleQutation);
            s = Regex.Replace(s, @"""[^""]+""", ev);

            s = SearchHelper.convertToSpaces(s);
            s = SearchHelper.replaceArabicLetters(s);
            s = SearchHelper.removeExtraSpaces(s);
            s = SearchHelper.ConvertTermsToDoubleQuotaion(s, false);
            s = Regex.Replace(s, " to ", " TO ", RegexOptions.IgnoreCase);

            //unhide original doubl Quotation terms
            ev = new MatchEvaluator(SearchHelper.UnHideDoubleQutation);
            s = Regex.Replace(s, "@@@@@", ev);

            return s;
        }

        public static string prepare(string SearchStatement, string[] indexesPathes, params string[] defaultFields)
        {
            doubleQutationQueue = new Queue<string>();
            string s = SearchStatement;

            //hide  OriginalDoubleQutation before normalize
            doubleQutationQueue = new Queue<string>();
            MatchEvaluator ev = new MatchEvaluator(SearchHelper.HideDoubleQutation);
            s = Regex.Replace(s, @"""[^""]+""", ev);

            s = SearchHelper.convertToSpaces(s);
            s = SearchHelper.replaceArabicLetters(s);
            s = SearchHelper.removeExtraSpaces(s);
            s = SearchHelper.ConvertTermsToDoubleQuotaion(s, false);
            s = Regex.Replace(s, " to ", " TO ", RegexOptions.IgnoreCase);

            //search fields
            string[] luceneSearchfields = SearchHelper.getSearchFields(indexesPathes);

            FieldsTemplateSatatement = "";
            for (int i = 0; i < luceneSearchfields.Length; i++)
            {
                //normalize searchStatement (lucene case sensitive)
                s = Regex.Replace(s, "\\b" + luceneSearchfields[i] + ": ", luceneSearchfields[i] + ": ", RegexOptions.IgnoreCase);

                if (defaultFields != null && defaultFields.Length > 0)
                {
                    for (int j = 0; j < defaultFields.Length; j++)
                    {
                        if (defaultFields[j].ToLower() == luceneSearchfields[i].ToLower())
                        {
                            defaultFields[j] = luceneSearchfields[i];
                            if (FieldsTemplateSatatement == "")
                            {
                                FieldsTemplateSatatement = "( " + luceneSearchfields[i] + ": ( @@@@@ ) )";
                            }
                            else
                            {
                                FieldsTemplateSatatement += " OR ( " + luceneSearchfields[i] + ": ( @@@@@ ) )";
                            }
                            break;
                        }
                    }
                }
                else
                {
                    if (FieldsTemplateSatatement == "")
                    {
                        FieldsTemplateSatatement = "( " + luceneSearchfields[i] + ": ( @@@@@ ) )";
                    }
                    else
                    {
                        FieldsTemplateSatatement += " OR ( " + luceneSearchfields[i] + ": ( @@@@@ ) )";
                    }
                }
            }
            FieldsTemplateSatatement = string.Format("( {0} )", FieldsTemplateSatatement);


            //unhide doubl Quotation terms
            ev = new MatchEvaluator(SearchHelper.UnHideDoubleQutation);
            s = Regex.Replace(s, "@@@@@", ev);

            //get advanced Query
            s = SearchHelper.getAdvencedQueryString(s, FieldsTemplateSatatement);

            return s;
        }


        //add spaces- remove tabs and new lines  
        private static string convertToSpaces(string statement)
        {
            string s = statement;
            s = Regex.Replace(s, @"\(", " ( ");
            s = Regex.Replace(s, @"\)", " ) ");
            s = Regex.Replace(s, @"\s", " ");// \r\n\t
            return s;
        }

        //replace arabic letters in arabic satement
        private static string replaceArabicLetters(string statement)
        {
            string s = statement;
            s = Regex.Replace(s, "[أآإ]", "ا");
            s = Regex.Replace(s, "ة", "ه");
            s = Regex.Replace(s, "ى", "ي");
            s = Regex.Replace(s, "؟", "?");
            s = Regex.Replace(s, @"[ًٌٍَُِّْ~ـ]", "");//علامات الترقيم
            s = Regex.Replace(s, @"\bوليس\b", " و ليس ");
            s = Regex.Replace(s, @"\bاوليس\b", " او ليس ");
            s = Regex.Replace(s, @"\bو\b", " AND ");
            s = Regex.Replace(s, @"\bاو\b", " OR ");
            s = Regex.Replace(s, @"\bليس\b", " NOT ");
            return s;
        }

        //remove spaces 
        private static string removeExtraSpaces(string statement)
        {
            string s = statement;
            s = Regex.Replace(s, @"\bعبد\b\s*", " عبد");
            s = Regex.Replace(s, @"\bابو\b\s*", " ابو");
            s = Regex.Replace(s, @"\s+:", ":");
            s = Regex.Replace(s, @"\s+", " ");
            s = s.Trim();
            return s;
        }

        //doubleQuotaionStatement 
        private static string ConvertTermsToDoubleQuotaion(string statement, bool hideOriginalDoubleQuotation)
        {
            string s = statement;

            if (hideOriginalDoubleQuotation)
            {
                //hide doubl Quotation terms
                MatchEvaluator ev = new MatchEvaluator(SearchHelper.HideDoubleQutation);
                s = Regex.Replace(s, @"""[^""]+""", ev);
            }

            //insert double Quotation between keywords 
            s = Regex.Replace(s, @"\b", @"""");


            //remove field from double qotation
            MatchEvaluator ev2 = new MatchEvaluator(SearchHelper.removeFieldDoubleQuotation);
            s = Regex.Replace(s, @"""\w+"":", ev2);

            ////remove double Quotaion from keywords with wild cards
            //MatchEvaluator ev3 = new MatchEvaluator(SearchHelper.removeDoubleQuotationFromKeyWordWithWildCard);
            //s = Regex.Replace(s, "\"?\\w+\"[\\?\\*~]+[^\\s]*", ev3);


            //remove joiner from double Qutation and other settings
            s = Regex.Replace(s, @"""AND""", "AND", RegexOptions.IgnoreCase);
            s = Regex.Replace(s, @"""OR""", "OR", RegexOptions.IgnoreCase);
            s = Regex.Replace(s, @"""NOT""", "NOT", RegexOptions.IgnoreCase);
            s = Regex.Replace(s, @"""\s*""", " ");
            s = Regex.Replace(s, @"\[\s*""", "[");
            s = Regex.Replace(s, @"""\s*\]", "]");
            s = Regex.Replace(s, @"\{\s*""", "{");
            s = Regex.Replace(s, @"""\s*\}", "}");

            s = Regex.Replace(s, @"""\s*–\s*""", " – ");
            s = Regex.Replace(s, @"""\s*-\s*""", " - ");
            s = Regex.Replace(s, @"""\s*ـ\s*""", " ـ ");
            s = Regex.Replace(s, @"""\s*_\s*""", " _ ");

            if (hideOriginalDoubleQuotation)
            {
                //unhide doubl Quotation terms
                MatchEvaluator ev = new MatchEvaluator(SearchHelper.UnHideDoubleQutation);
                s = Regex.Replace(s, "@@@@@", ev);
            }

            return s.Trim();
        }

        //get advanced query string
        private static string getAdvencedQueryString(string searchStatement, string FieldsTemplateSatatement)
        {
            fieldsQueue = new Queue<string>();

            //hide fields statement
            string s = searchStatement;
            Regex reg = new Regex(@"\b\w+:\s*(\(|""[^""]+""|\[[\w\s]+\]|\{[\w\s]+\})");
            Match match = reg.Match(s);
            while (match.Success)
            {
                if (match.Value.EndsWith("("))
                {
                    int openeingBrackets = match.Index + match.Length - 1;
                    int endingBrackets = WebHelper.Helpers.StringHelper.getpair(s, openeingBrackets);
                    string fieldStatement = s.Substring(match.Index, endingBrackets - match.Index + 1);
                    Regex newReg = new Regex(Regex.Escape(fieldStatement));
                    fieldsQueue.Enqueue(fieldStatement);
                    s = newReg.Replace(s, "@@@@@", 1);
                }
                else
                {
                    fieldsQueue.Enqueue(match.Value);
                    s = reg.Replace(s, "@@@@@", 1);
                }
                match = reg.Match(s);
            }


            //construct statement
            MatchEvaluator ev = new MatchEvaluator(SearchHelper.constructStatement);
            s = Regex.Replace(s, @"""[^""]+""", ev);



            //unhide fields statement
            ev = new MatchEvaluator(SearchHelper.UnHideFields);
            s = Regex.Replace(s, "@@@@@", ev);

            return s;
        }

        //hide double qutation
        private static string HideDoubleQutation(Match m)
        {
            doubleQutationQueue.Enqueue(m.Value);
            return "@@@@@";
        }

        //unhide double qutation
        private static string UnHideDoubleQutation(Match m)
        {
            return doubleQutationQueue.Dequeue();
        }

        //unhide fields 
        private static string UnHideFields(Match m)
        {
            return fieldsQueue.Dequeue();
        }


        //remove field double Quotation
        private static string removeFieldDoubleQuotation(Match m)
        {
            string s = m.Value;
            return s.Replace(@"""", "");
        }


        //construct statement
        private static string constructStatement(Match m)
        {
            return FieldsTemplateSatatement.Replace("@@@@@", m.Value);
        }

        //remove double Quotation from keyword with wild cards
        private static string removeDoubleQuotationFromKeyWordWithWildCard(Match m)
        {
            return m.Value.Replace(@"""", "");
        }

        public static string[] getSearchFields(string[] indexesPathes)
        {
            List<string> searchFilds = new List<string>();
            for (int i = 0; i < indexesPathes.Length; i++)
            {
                FSDirectory fs = FSDirectory.Open(new System.IO.DirectoryInfo(indexesPathes[i]));
                IndexReader reader = IndexReader.Open(fs,true);
                //search fields
                ICollection<string> luceneSearchfields = reader.GetFieldNames(IndexReader.FieldOption.INDEXED);
                foreach (string key in luceneSearchfields)
                {
                    if (!searchFilds.Contains(key))
                    {
                        searchFilds.Add(key);
                    }
                }
                fs.Close();
                reader.Close();
            }
            return searchFilds.ToArray();
        }
    }
}
